<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="UTF-8" />
		<meta name="viewport" content="width=device-width, initial-scale=1.0" />
		<title>gltf</title>
		<style>
			* {
				box-sizing: border-box;
			}
			html {
				background: #692a84;
				background: linear-gradient(316deg, #0e0f2a, #060913);
				height: 100%;
				width: 100%;
			}
			body {
				font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
				overflow: hidden;
				height: 100%;
				width: 100%;
				margin: 0;
				padding: 0;
			}
			#app {
				height: 100%;
				width: 100%;
			}
		</style>
	</head>

	<body>
		<div id="app"></div>
		<script type="module">
			import { Model, Context, Texture, RenderTarget, Attachment, Sampler, Buffer } from "../../dist/index.js";
			import { mat4, vec3 } from "../lib/wgpu-matrix/wgpu-matrix.module.js";
			import { import_Shader, export_shader, renderVert, renderFrag, simulateShader } from "../asset/shader.js";
			const init = async () => {
				let textureWidth = 1;
				let textureHeight = 1;
				let numMipLevels = 1;
				let texture;
				const computeModels = [];
				const numParticles = 50000;
				const particleInstanceByteSize =
					3 * 4 + // position
					1 * 4 + // lifetime
					4 * 4 + // color
					3 * 4 + // velocity
					1 * 4 + // padding
					0;
				const response = await fetch(new URL("../textures/webgpu.png", import.meta.url).toString());
				const imageBitmap = await createImageBitmap(await response.blob());
				const vertexData = [-1.0, -1.0, 1.0, -1.0, -1.0, 1.0, -1.0, 1.0, 1.0, -1.0, 1.0, 1.0];
				const context = new Context({
					canvas: null,
					container: document.getElementById("app"),
					pixelRatio: 1
				});
				const { canvas, presentationFormat } = context;
				await context.init();
				/*******************************Texture*******************************/
				// Calculate number of mip levels required to generate the probability map
				while (textureWidth < imageBitmap.width || textureHeight < imageBitmap.height) {
					textureWidth *= 2;
					textureHeight *= 2;
					numMipLevels++;
				}
				texture = new Texture({
					textureDescriptor: {
						format: "rgba8unorm",
						usage:
							GPUTextureUsage.TEXTURE_BINDING |
							GPUTextureUsage.STORAGE_BINDING |
							GPUTextureUsage.COPY_DST |
							GPUTextureUsage.RENDER_ATTACHMENT,
						size: {
							width: imageBitmap.width,
							height: imageBitmap.height,
							depth: 1
						},
						mipLevelCount: numMipLevels
					},
					data: {
						source: imageBitmap,
						width: imageBitmap.width,
						height: imageBitmap.height
					}
				});
				texture.update(context.device);

				const buffer_a = Buffer.createStorageBuffer(
					"buffer_a",
					context.device,
					textureWidth * textureHeight * 4
				);
				const buffer_b = Buffer.createStorageBuffer(
					"buffer_b",
					context.device,
					textureWidth * textureHeight * 4
				);
				const particlesBuffer = Buffer.create(
					"particlesBuffer",
					context.device,
					GPUBufferUsage.VERTEX | GPUBufferUsage.STORAGE,
					null,
					numParticles * particleInstanceByteSize
				);
				const { width, height, depth } = context.presentationSize;
				/**********************RenderTarget***************************/

				const colorAttachment = new Attachment(
					{ r: 0.0, g: 0.0, b: 0.0, a: 1.0 },
					{
						textureView: () => {
							return context.context.getCurrentTexture().createView();
						}
					}
				);
				const depthTexture = new Texture({
					label: "resolveDepth",
					textureDescriptor: {
						size: { width, height, depth },
						format: "depth24plus",
						usage: GPUTextureUsage.RENDER_ATTACHMENT
					}
				});
				const depthAttachment = new Attachment(1.0, { texture: depthTexture });
				const canvasRenderTarget = new RenderTarget("render", [colorAttachment], depthAttachment);
				const aspect = canvas.width / canvas.height;
				const projection = mat4.perspective((2 * Math.PI) / 5, aspect, 1, 100.0);
				const view = mat4.create();
				const mvp = mat4.create();
				const renderModel = new Model({
					shaderId: "model",
					vert: renderVert,
					frag: renderFrag,
					vertexBuffers: [
						{
							stepMode: "instance",
							uid: "instanceAttr",
							arrayStride: 48,
							attributes: {
								buffer: {
									names: ["position", "lifetime", "color", "velocity"],
									buffer: particlesBuffer,
									itemSizes: [3, 1, 4, 3]
								}
							}
						},
						{
							stepMode: "vertex",
							uid: "vertAttr",
							attributes: {
								position: {
									size: 2,
									value: vertexData
								}
							}
						}
					],
					uniformBuffers: [
						{
							type: "uniform",
							uid: "systemMatrix",
							uniforms: {
								modelViewProjectionMatrix: {
									type: "mat4x4<f32>",
									value: () => {
										return mvp;
									}
								},
								right: {
									type: "vec3<f32>",
									value: () => {
										return [view[0], view[4], view[8]];
									}
								},
								up: {
									type: "vec3<f32>",
									value: () => {
										return [view[1], view[5], view[9]];
									}
								}
							}
						}
					],
					renderState: {
						targets: [
							{
								format: presentationFormat,
								blend: {
									color: {
										srcFactor: "src-alpha",
										dstFactor: "one",
										operation: "add"
									},
									alpha: {
										srcFactor: "zero",
										dstFactor: "one",
										operation: "add"
									}
								}
							}
						],
						primitive: {
							topology: "triangle-list"
						},
						depthStencil: {
							depthWriteEnabled: false,
							depthCompare: "less",
							format: "depth24plus"
						}
					},
					draw: {
						count: 6,
						instanceCount: numParticles
					}
				});
				const simulateModel = new Model({
					shaderId: "simulate",
					compute: simulateShader,
					uniformBuffers: [
						{
							type: "uniform",
							uid: "SimulationParams",
							usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
							visibility: GPUShaderStage.COMPUTE,
							uniforms: {
								seed: {
									type: "vec4<f32>",
									value: () => {
										return [
											Math.random() * 100,
											Math.random() * 100, // seed.xy
											1 + Math.random(),
											1 + Math.random()
										];
									}
								},
								deltaTime: { type: "f32", value: 0.04 }
							}
						}, //根据uniforms创建uniformBuffer
						{
							type: "storage",
							uid: "particle",
							visibility: GPUShaderStage.COMPUTE,
							buffer: particlesBuffer
						}
					],
					uniformTextureAndSampler: {
						tex_in: {
							type: "texture",
							value: texture,
							visibility: GPUShaderStage.COMPUTE
						}
					},
					dispatch: { x: Math.ceil(numParticles / 64) }
				});
				const commandEncoder = context.device.createCommandEncoder();
				for (let level = 0; level < numMipLevels; level++) {
					const levelWidth = textureWidth >> level;
					const levelHeight = textureHeight >> level;
					const shader = level == 0 ? import_Shader : export_shader;
					const compueModel = new Model({
						shaderId: "compute_" + level,
						compute: shader,
						uniformBuffers: [
							{
								type: "uniform",
								uid: "ubo",
								visibility: GPUShaderStage.COMPUTE,
								binding: 0,
								uniforms: {
									ubo: {
										type: "f32",
										value: textureWidth
									}
								}
							}, //根据uniforms创建uniformBuffer
							{
								type: "storage",
								uid: "buf_in",
								binding: 1,
								visibility: GPUShaderStage.COMPUTE,
								buffer: level & 1 ? buffer_a : buffer_b // buffer为数据源
							},
							{
								type: "storage",
								uid: "buf_out",
								binding: 2,
								visibility: GPUShaderStage.COMPUTE,
								buffer: level & 1 ? buffer_b : buffer_a
							}
						],
						uniformTextureAndSampler: {
							tex_in_out: {
								type: level === 0 ? "texture" : "storageTexture",
								value: texture,
								visibility: GPUShaderStage.COMPUTE,
								binding: 3,
								textureView: texture.gpuTexture.createView({
									format: "rgba8unorm",
									dimension: "2d",
									baseMipLevel: level,
									mipLevelCount: 1
								})
							}
						},
						dispatch: { x: Math.ceil(levelWidth / 64), y: levelWidth }
					});
					const passEncoder = commandEncoder.beginComputePass();
					compueModel.compute({ device: context.device, passEncoder });
					passEncoder.end();
				}
				context.device.queue.submit([commandEncoder.finish()]);
				const updateMatrix = () => {
					mat4.identity(view);
					mat4.translate(view, vec3.fromValues(0, 0, -3), view);
					mat4.rotateX(view, Math.PI * -0.2, view);
					mat4.multiply(projection, view, mvp);
				};
				const excuteRender = () => {
					const passEncoder = canvasRenderTarget.beginRenderPass(context.device);
					renderModel.render({ device: context.device, passEncoder });
					canvasRenderTarget.endRenderPass();
				};
				const excuteCompute = () => {
					const commandEncoder = context.device.createCommandEncoder();
					const passEncoder = commandEncoder.beginComputePass();
					simulateModel.compute({ device: context.device, passEncoder });
					passEncoder.end();
					context.device.queue.submit([commandEncoder.finish()]);
				};
				function animate() {
					requestAnimationFrame(animate);
					updateMatrix();
					excuteCompute();
					excuteRender();
				}
				animate();
			};
			init();
		</script>
	</body>
</html>
